##Copyright (c) 2013, sahil singh
##
##All rights reserved.
##
##Redistribution and use in source and binary forms, with or without modification,
##are permitted provided that the following conditions are met:
##
##    * Redistributions of source code must retain the above copyright notice,
##      this list of conditions and the following disclaimer.
##    * Redistributions in binary form must reproduce the above copyright notice,
##      this list of conditions and the following disclaimer in the documentation
##      and/or other materials provided with the distribution.
##    * Neither the name of NeuroPy nor the names of its contributors
##      may be used to endorse or promote products derived from this software
##      without specific prior written permission.
##
##THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
##"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
##LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
##A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
##CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
##EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
##PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
##PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
##LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
##NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
##SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import serial
import _thread
import sys
import os
import logging

# 添加项目根目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir)
if project_root not in sys.path:
    sys.path.append(project_root)

from exception.base_exceptions import DeviceConnectionException
from exception.exception_handler import global_exception_handler

# 设置logger
logger = logging.getLogger(__name__)

class NeuroPy(object):
    """NeuroPy libraby, to get data from neurosky mindwave.
    Initialising: object1=NeuroPy("COM6",57600) #windows
    After initialising , if required the callbacks must be set
    then using the start method the library will start fetching data from mindwave
    i.e. object1.start()
    similarly stop method can be called to stop fetching the data
    i.e. object1.stop()

    The data from the device can be obtained using either of the following methods or both of them together:
    
    Obtaining value: variable1=object1.attention #to get value of attention
    #other variables: attention,meditation,rawValue,delta,theta,lowAlpha,highAlpha,lowBeta,highBeta,lowGamma,midGamma, poorSignal and blinkStrength
    
    Setting callback:a call back can be associated with all the above variables so that a function is called when the variable is updated. Syntax: setCallBack("variable",callback_function)
    for eg. to set a callback for attention data the syntax will be setCallBack("attention",callback_function)"""
    
    def __init__(self,port,baudRate=57600):
        self.__port,self.__baudRate=port,baudRate
        # 初始化所有脑波数据属性为实例变量
        self.__attention=0
        self.__meditation=0
        self.__rawValue=0
        self.__delta=0
        self.__theta=0
        self.__lowAlpha=0
        self.__highAlpha=0
        self.__lowBeta=0
        self.__highBeta=0
        self.__lowGamma=0
        self.__midGamma=0    
        self.__poorSignal=0
        self.__blinkStrength=0
        self.srl=None
        self.threadRun=True #controlls the running of thread
        self.callBacksDictionary={} #keep a track of all callbacks
        # 自动断开检测相关属性
        self.__poor_signal_count = 0
        self.__poor_signal_threshold = 200  # poorSignal阈值
        self.__poor_signal_duration = 10   # 持续10次检测到高poorSignal就断开
        self.__auto_disconnect_enabled = True
        
    def __del__(self):
        if hasattr(self, 'srl') and self.srl is not None:
            try:
                self.srl.close()
            except:
                pass
    
    def start(self):
        """starts packetparser in a separate thread"""
        try:
            self.threadRun=True
            self.srl=serial.Serial(self.__port,self.__baudRate)
            _thread.start_new_thread(self.__packetParser,(self.srl,))
        except Exception as e:
            connection_error = DeviceConnectionException(
                f"设备连接启动失败: {e}",
                port=self.__port
            )
            global_exception_handler.handle_exception(connection_error)
            raise connection_error
   
    def __packetParser(self,srl):
        "packetParser runs continously in a separate thread to parse packets from mindwave and update the corresponding variables"
        #srl.open()
        while self.threadRun:
            try:
                p1=srl.read(1).hex() #read first 2 packets
                p2=srl.read(1).hex()
                if not p1 or not p2:  # 检查包头字节是否为空
                    logger.warning("接收到空包头字节，跳过处理")
                    continue
                while p1!='aa' or p2!='aa':
                    p1=p2
                    p2=srl.read(1).hex()
                    if not p2:  # 检查新读取的字节是否为空
                        logger.warning("接收到空字节，跳过处理")
                        break
                else:
                    #a valid packet is available
                    payload=[]
                    checksum=0;
                    payloadLengthByte = srl.read(1).hex()
                    if not payloadLengthByte:  # 检查载荷长度字节是否为空
                        logger.warning("接收到空载荷长度字节，跳过处理")
                        continue
                    payloadLength=int(payloadLengthByte,16)
                    for i in range(payloadLength):
                        tempPacket=srl.read(1).hex()
                        if not tempPacket:  # 检查空字符串
                            logger.warning("接收到空数据包，跳过处理")
                            break
                        payload.append(tempPacket)
                        checksum+=int(tempPacket,16)
                    checksum=~checksum&0x000000ff
                    checksumByte = srl.read(1).hex()
                    if not checksumByte:  # 检查校验和字节是否为空
                        logger.warning("接收到空校验和字节，跳过处理")
                        continue
                    if checksum==int(checksumByte,16):
                        i=0
                    while i<payloadLength:
                       code=payload[i]
                       # 添加调试信息，每100个包打印一次接收到的代码
                       if hasattr(self, '_debug_packet_count'):
                           self._debug_packet_count += 1
                       else:
                           self._debug_packet_count = 1
                       
                       if self._debug_packet_count % 100 == 0:
                            # print(f"[DEBUG] 接收到数据包代码: {code}")  # 删除冗余的DEBUG打印
                            pass
                       
                       if(code=='02'):#poorSignal
                           i=i+1; self.poorSignal=int(payload[i],16)
                       elif(code=='04'):#attention
                           i=i+1; self.attention=int(payload[i],16)
                       elif(code=='05'):#meditation
                           i=i+1; self.meditation=int(payload[i],16)
                       elif(code=='16'):#blink strength
                           i=i+1; self.blinkStrength=int(payload[i],16)
                       elif(code=='80'):#raw value
                           i=i+1 #for length/it is not used since length =1 byte long and always=2
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; self.rawValue=val0*256+int(payload[i],16)
                           if self.rawValue>32768 :
                               self.rawValue=self.rawValue-65536
                       elif(code=='83'):#ASIC_EEG_POWER
                           i=i+1;#for length/it is not used since length =1 byte long and always=2
                           #delta:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.delta=val0*65536+val1*256+int(payload[i],16)
                           #theta:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.theta=val0*65536+val1*256+int(payload[i],16)
                           #lowAlpha:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.lowAlpha=val0*65536+val1*256+int(payload[i],16)
                           #highAlpha:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.highAlpha=val0*65536+val1*256+int(payload[i],16)
                           #lowBeta:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.lowBeta=val0*65536+val1*256+int(payload[i],16)
                           #highBeta:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.highBeta=val0*65536+val1*256+int(payload[i],16)
                           #lowGamma:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.lowGamma=val0*65536+val1*256+int(payload[i],16)
                           #midGamma:
                           i=i+1; val0=int(payload[i],16)
                           i=i+1; val1=int(payload[i],16)
                           i=i+1; self.midGamma=val0*65536+val1*256+int(payload[i],16)
                       else:
                           pass
                       i=i+1
            except Exception as e:
                data_error = DeviceConnectionException(
                    f"设备数据解析失败: {e}",
                    port=str(getattr(self, 'port', 'unknown'))
                )
                global_exception_handler.handle_exception(data_error)
                # 继续循环，不中断数据接收


        
    def stop(self):
        "stops packetparser's thread and releases com port i.e disconnects mindwave"
        self.threadRun=False
        if hasattr(self, 'srl') and self.srl is not None:
            try:
                self.srl.close()
            except Exception as e:
                connection_error = DeviceConnectionException(
                    f"设备串口关闭失败: {e}",
                    port=self.__port
                )
                global_exception_handler.handle_exception(connection_error)
            finally:
                self.srl = None
    
    def _auto_disconnect(self):
        """自动断开连接的内部方法"""
        try:
            print(f"[自动断开] 正在断开COM{self.__port}连接...")
            self.stop()
            print(f"[自动断开] COM{self.__port}连接已断开，端口已释放")
        except Exception as e:
            print(f"[自动断开] 断开连接时发生错误: {e}")
    
    def enable_auto_disconnect(self, enabled=True):
        """启用或禁用自动断开功能"""
        self.__auto_disconnect_enabled = enabled
        print(f"[自动断开] 功能已{'启用' if enabled else '禁用'}")
    
    def set_auto_disconnect_params(self, threshold=200, duration=10):
        """设置自动断开参数"""
        self.__poor_signal_threshold = threshold
        self.__poor_signal_duration = duration
        print(f"[自动断开] 参数已更新: 阈值={threshold}, 持续次数={duration}")
        
    
                    
                    
    def setCallBack(self,variable_name,callback_function):
        """Setting callback:a call back can be associated with all the above variables so that a function is called when the variable is updated. Syntax: setCallBack("variable",callback_function)
           for eg. to set a callback for attention data the syntax will be setCallBack("attention",callback_function)"""
        self.callBacksDictionary[variable_name]=callback_function
        
    #setting getters and setters for all variables
    
    #attention
    @property
    def attention(self):
        "Get value for attention"
        return self.__attention
    @attention.setter
    def attention(self,value):
        self.__attention=value
        if "attention" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["attention"](self.__attention)
            
    #meditation
    @property
    def meditation(self):
        "Get value for meditation"
        return self.__meditation
    @meditation.setter
    def meditation(self,value):
        self.__meditation=value
        if "meditation" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["meditation"](self.__meditation)
            
    #rawValue
    @property
    def rawValue(self):
        "Get value for rawValue"
        return self.__rawValue
    @rawValue.setter
    def rawValue(self,value):
        self.__rawValue=value
        if "rawValue" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["rawValue"](self.__rawValue)

    #delta
    @property
    def delta(self):
        "Get value for delta"
        return self.__delta
    @delta.setter
    def delta(self,value):
        self.__delta=value
        if "delta" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["delta"](self.__delta)

    #theta
    @property
    def theta(self):
        "Get value for theta"
        return self.__theta
    @theta.setter
    def theta(self,value):
        self.__theta=value
        if "theta" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["theta"](self.__theta)

    #lowAlpha
    @property
    def lowAlpha(self):
        "Get value for lowAlpha"
        return self.__lowAlpha
    @lowAlpha.setter
    def lowAlpha(self,value):
        self.__lowAlpha=value
        if "lowAlpha" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["lowAlpha"](self.__lowAlpha)

    #highAlpha
    @property
    def highAlpha(self):
        "Get value for highAlpha"
        return self.__highAlpha
    @highAlpha.setter
    def highAlpha(self,value):
        self.__highAlpha=value
        if "highAlpha" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["highAlpha"](self.__highAlpha)


    #lowBeta
    @property
    def lowBeta(self):
        "Get value for lowBeta"
        return self.__lowBeta
    @lowBeta.setter
    def lowBeta(self,value):
        self.__lowBeta=value
        if "lowBeta" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["lowBeta"](self.__lowBeta)

    #highBeta
    @property
    def highBeta(self):
        "Get value for highBeta"
        return self.__highBeta
    @highBeta.setter
    def highBeta(self,value):
        self.__highBeta=value
        if "highBeta" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["highBeta"](self.__highBeta)

    #lowGamma
    @property
    def lowGamma(self):
        "Get value for lowGamma"
        return self.__lowGamma
    @lowGamma.setter
    def lowGamma(self,value):
        self.__lowGamma=value
        if "lowGamma" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["lowGamma"](self.__lowGamma)

    #midGamma
    @property
    def midGamma(self):
        "Get value for midGamma"
        return self.__midGamma
    @midGamma.setter
    def midGamma(self,value):
        self.__midGamma=value
        if "midGamma" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["midGamma"](self.__midGamma)
    
    #poorSignal
    @property
    def poorSignal(self):
        "Get value for poorSignal"
        return self.__poorSignal
    @poorSignal.setter
    def poorSignal(self,value):
        self.__poorSignal=value
        
        # 自动断开检测逻辑
        if self.__auto_disconnect_enabled:
            if value >= self.__poor_signal_threshold:
                self.__poor_signal_count += 1
                if self.__poor_signal_count >= self.__poor_signal_duration:
                    print(f"[自动断开] 检测到设备信号质量持续较差 (poorSignal={value}), 自动断开连接")
                    self._auto_disconnect()
            else:
                # 信号质量恢复，重置计数器
                self.__poor_signal_count = 0
        
        if "poorSignal" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["poorSignal"](self.__poorSignal)
    
    #blinkStrength
    @property
    def blinkStrength(self):
        "Get value for blinkStrength"
        return self.__blinkStrength
    @blinkStrength.setter
    def blinkStrength(self,value):
        self.__blinkStrength=value
        if "blinkStrength" in self.callBacksDictionary: #if callback has been set, execute the function
            self.callBacksDictionary["blinkStrength"](self.__blinkStrength)
